package com.googlecode.jawb;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;

import com.googlecode.jawb.clues.types.DynamicLengthType;
import com.googlecode.jawb.clues.types.StaticLengthType;

/**
 * Loads data from object and writes them in binary form into output stream.
 */
public class OutputProcessor extends Processor {
	private OutputStream outputStream = null;
	private JAWB         jawb         = null;

	public OutputProcessor(OutputStream outputStream, Object whichObject, JAWB jawb) {
		super(whichObject);
		this.outputStream = outputStream;
		this.jawb         = jawb;
	}

	@Override
	protected void onPrimitiveField(Field field, BinaryType asType) {
		try {
			long    value = ((Number) field.get(processedObject)).longValue();
			byte[]  raw   = asType.toByteArray(value);

			outputStream.write(raw);
		} catch (IOException e) {
			this.setError(e);
			this.kill();
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Internal error: Invalid argument has been passed while trying to read a field.", e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Internal error: Field could not be read due to access policy.", e);
		}
	}

	@Override
	protected void onObjectField(Field field, Class<?> asClass) {
		try {
			jawb.saveTo(outputStream, field.get(processedObject));
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Internal error: Field could not be read due to access policy.", e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Internal error: Invalid argument has been passed while trying to read a field.", e);
		} catch (IOException e) {
			this.setError(e);
			this.kill();
		}
	}

	@Override
	protected void onPrimitiveArray(Field field, int length, BinaryType asType) {
		try {
			Object array = field.get(processedObject);

			if (length == 0) {
				length = Array.getLength(array);
			}

			for (int i = 0; i < length; i++) {
				long   value = ((Number) Array.get(array, i)).longValue();
				byte[] raw   = asType.toByteArray(value);

				outputStream.write(raw);
				i = annotationParser.callItemPostReadListener(i, ListenerTarget.OUTPUT);
			}
		} catch (IOException e) {
			this.setError(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Internal error: Field could not be read due to access policy.", e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Internal error: Invalid argument has been passed while trying to read a field.", e);
		}
	}

	@Override
	protected void onObjectArray(Field field, int length, Class<?> asClass) {
		try {
			Object array = field.get(processedObject);

			if (length == 0) {
				length = Array.getLength(array);
			}

			for (int i = 0; i < length; i++) {
				jawb.saveTo(outputStream, Array.get(array, i));
				i = annotationParser.callItemPostReadListener(i, ListenerTarget.OUTPUT);
			}
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Internal error: Field could not be read due to access policy.", e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Internal error: Invalid argument has been passed while trying to read a field.", e);
		} catch (IOException e) {
			this.setError(e);
			this.kill();
		}
	}

	@Override
	protected void onStaticallyConvertedField(Field field, StaticLengthType staticLengthType) {
		try {
			Object fieldValue = field.get(processedObject);
			byte[] converted  = staticLengthType.toByteArray(fieldValue);
			
			this.outputStream.write(converted);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Internal error: Unable to obtain value of field (invalid argument # static converter).", e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Value of field could not be obtained due to access policy (static converter).", e);
		} catch (IOException e) {
			this.setError(e);
			this.kill();
		}
	}

	@Override
	protected void onDynamicallyConvertedField(Field field, DynamicLengthType dynamicLengthType) {
		try {
			Object fieldValue = field.get(processedObject);
			byte[] converted  = dynamicLengthType.toByteArray(fieldValue);
			
			this.outputStream.write(converted);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException("Internal error: Unable to obtain value of field (invalid argument # static converter).", e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Value of field could not be obtained due to access policy (dynamic converter).", e);
		} catch (IOException e) {
			this.setError(e);
			this.kill();
		}
	}
	
	/**
	 * Checks if an errors has occurred while writing to the file. You should
	 * always call this method immediately after {@link #process()}.
	 * 
	 * @throws IOException
	 *             An I/O error.
	 */
	protected void check() throws IOException {
		if (hasError()) {
			if (getError() instanceof IOException) {
				throw (IOException) getError();
			} else {
				throw new RuntimeException("Unknown error has occurred.", getError());
			}
		}
	}
}
